package fgin

import (
	"context"
	"fmt"
	"net"
	"time"

	"gitee.com/wu-jin-feng/fgin/fgin_plugs/fotel/ftracer"
	"github.com/opentracing/opentracing-go"
	"github.com/redis/go-redis/v9"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
)

var (
	redisClient  redis.UniversalClient
	redisClients = make(map[string]redis.UniversalClient)
)

func initRedis() {
	if len(fginConfig.Redis) == 0 {
		// 没有配置redis
		return
	}
	// 使用go-redis底层默认的连接池，以及读写超时控制
	for i, v := range fginConfig.Redis {
		if v.RedisName == "" {
			panic("redis初始化失败,存在redis_name配置为空")
		}
		vRedisClt := redis.NewUniversalClient(&redis.UniversalOptions{
			PoolFIFO: true, // 使用FIFO策略，防止连接池使用不均衡
			Addrs:    v.Addrs,
			Username: v.UserName,
			Password: v.PassWord,
			DB:       v.Db,
		})
		// 开启日志
		vRedisClt.AddHook(&redisLoggerHook{})
		// 开启otel链路追踪
		if fginConfig.Otel.EnableTracerRedis {
			vRedisClt.AddHook(&redisOtelTracingHook{})
		}
		// 是否开启redis jaeger追踪
		if fginConfig.Jaeger.EnableRedis {
			vRedisClt.AddHook(&redisTracingHook{})
		}
		redisClients[v.RedisName] = vRedisClt
		// 兼容v1，曾经只能使用一个redis
		if i == 0 {
			redisClient = redisClients[v.RedisName]
		}
		// 检测redis
		if err := redisClients[v.RedisName].Ping(context.Background()).Err(); err != nil {
			panic(fmt.Sprintf("redis %s 初始化失败,err:%s", v.RedisName, err.Error()))
		}
	}
}

// redis 日志
type redisLoggerHook struct{}

// DialHook 实现 Dial 钩子
func (h *redisLoggerHook) DialHook(next redis.DialHook) redis.DialHook {
	return func(ctx context.Context, network, addr string) (net.Conn, error) {
		conn, err := next(ctx, network, addr)
		return conn, err
	}
}

// ProcessHook 实现命令处理钩子
func (h *redisLoggerHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
	return func(ctx context.Context, cmd redis.Cmder) error {
		nowTime := time.Now()
		err := next(ctx, cmd)
		consTime := time.Since(nowTime)
		logSuger.Info(fmt.Sprintf("redis cons: %v, cmd: %v", consTime, cmd), ctx)
		return err
	}
}

// ProcessPipelineHook 实现管道处理钩子
func (h *redisLoggerHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
	return func(ctx context.Context, cmds []redis.Cmder) error {
		// 执行命令
		nowTime := time.Now()
		err := next(ctx, cmds)
		consTime := time.Since(nowTime)
		logSuger.Info(fmt.Sprintf("redis cons: %v, cmd: %v", consTime, cmds), ctx)
		return err
	}
}

// redis otel 追踪
type redisOtelTracingHook struct{}

// DialHook 实现 Dial 钩子
func (h *redisOtelTracingHook) DialHook(next redis.DialHook) redis.DialHook {
	return func(ctx context.Context, network, addr string) (net.Conn, error) {
		var (
			err  error
			conn net.Conn
		)
		// otel trace 部分
		tracer := ftracer.NewTracer(trace.SpanKindClient, "go-redis")
		_, otelSpan := tracer.Start(ctx, "go-redis.dial")
		defer tracer.End(otelSpan, err)
		conn, err = next(ctx, network, addr)
		return conn, err
	}
}

// ProcessHook 实现命令处理钩子
func (h *redisOtelTracingHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
	return func(ctx context.Context, cmd redis.Cmder) error {
		var (
			err error
		)
		// otel trace 的部分
		tracer := ftracer.NewTracer(trace.SpanKindClient, "go-redis")
		_, otelSpan := tracer.Start(ctx, "go-redis."+cmd.Name())
		defer tracer.End(otelSpan, err)
		otelSpan.SetAttributes(
			attribute.String("method", "REDIS"),
		)
		otelSpan.AddEvent("redis_handle", trace.WithAttributes(
			attribute.String("redis_command", cmd.Name()),
			attribute.String("redis_key", extractKey(cmd)),
		))
		// 执行命令
		err = next(ctx, cmd)
		return err
	}
}

// ProcessPipelineHook 实现管道处理钩子
func (h *redisOtelTracingHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
	return func(ctx context.Context, cmds []redis.Cmder) error {
		var (
			err error
		)
		// otel trace的部分
		tracer := ftracer.NewTracer(trace.SpanKindClient, "go-redis")
		_, otelSpan := tracer.Start(ctx, "go-redis.pipeline")
		defer tracer.End(otelSpan, err)
		otelSpan.SetAttributes(
			attribute.String("method", "REDIS"),
			attribute.String("commands_len", fmt.Sprintf("%v", len(cmds))),
		)
		// 记录前3个命令
		if len(cmds) > 0 {
			otelSpan.AddEvent("first_command_handle", trace.WithAttributes(
				attribute.String("first_command", cmds[0].Name()),
				attribute.String("first_key", extractKey(cmds[0])),
			))
		}
		if len(cmds) > 1 {
			otelSpan.AddEvent("second_command_handle", trace.WithAttributes(
				attribute.String("second_command", cmds[1].Name()),
				attribute.String("second_key", extractKey(cmds[1])),
			))
		}
		if len(cmds) > 2 {
			otelSpan.AddEvent("third_command_handle", trace.WithAttributes(
				attribute.String("third_command", cmds[2].Name()),
				attribute.String("third_key", extractKey(cmds[2])),
			))
		}
		// 执行命令
		err = next(ctx, cmds)
		if err != nil {
			return err
		}
		// 检查命令级错误
		for _, cmd := range cmds {
			if cmd.Err() != nil && cmd.Err() != redis.Nil {
				err = fmt.Errorf("error_cmd_name: %v, err: %v", cmd.Name(), cmd.Err())
				break
			}
		}
		return err
	}
}

// redis jaeger追踪
type redisTracingHook struct{}

// DialHook 实现 Dial 钩子
func (h *redisTracingHook) DialHook(next redis.DialHook) redis.DialHook {
	return func(ctx context.Context, network, addr string) (net.Conn, error) {
		span := JaegerSpan("go-redis.dial", ctx)
		if span == nil {
			return next(ctx, network, addr)
		}
		defer span.Finish()
		conn, err := next(ctx, network, addr)
		return conn, err
	}
}

// ProcessHook 实现命令处理钩子
func (h *redisTracingHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
	return func(ctx context.Context, cmd redis.Cmder) error {
		// 从上下文获取父Span
		span := JaegerSpan("go-redis."+cmd.Name(), ctx)
		if span == nil {
			return next(opentracing.ContextWithSpan(ctx, span), cmd)
		}
		defer span.Finish()
		span.SetTag("method", "REDIS")
		span.LogKV("redis_command", cmd.Name(), "redis_key", extractKey(cmd))

		// 执行命令
		err := next(opentracing.ContextWithSpan(ctx, span), cmd)
		// 记录错误
		if err != nil && err != redis.Nil {
			span.SetTag("error", true)
			span.LogKV("error", err.Error())
		}
		return err
	}
}

// ProcessPipelineHook 实现管道处理钩子
func (h *redisTracingHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
	return func(ctx context.Context, cmds []redis.Cmder) error {
		// 从上下文获取父Span
		span := JaegerSpan("go-redis.pipeline", ctx)
		if span == nil {
			return next(opentracing.ContextWithSpan(ctx, span), cmds)
		}
		span.SetTag("method", "REDIS")
		span.SetTag("commands_len", len(cmds))
		defer span.Finish()

		// 记录前3个命令
		if len(cmds) > 0 {
			span.LogKV("first_command", cmds[0].Name(), "first_key", extractKey(cmds[0]))
		}
		if len(cmds) > 1 {
			span.LogKV("second_command", cmds[1].Name(), "second_key", extractKey(cmds[1]))
		}
		if len(cmds) > 2 {
			span.LogKV("third_command", cmds[2].Name(), "third_key", extractKey(cmds[2]))
		}

		// 执行命令
		err := next(opentracing.ContextWithSpan(ctx, span), cmds)

		// 检查错误
		if err != nil {
			span.SetTag("error", true)
			span.LogKV("error", err.Error())
		} else {
			// 检查命令级错误
			for _, cmd := range cmds {
				if cmd.Err() != nil && cmd.Err() != redis.Nil {
					span.SetTag("error", true)
					span.LogKV("error", cmd.Err().Error(), "error_cmd_name", cmd.Name())
					break
				}
			}
		}
		return err
	}
}

// 辅助函数：提取Redis键名
func extractKey(cmd redis.Cmder) string {
	if len(cmd.Args()) < 2 {
		return ""
	}

	if key, ok := cmd.Args()[1].(string); ok {
		return key
	}
	return ""
}
